<template>
  <div id="login">

    <div id="bgd">
      <canvas id='myCanvas' :width='width' :height='height'></canvas>
    </div>

    <div id="loginBox">

      <h3 style="text-align: center">登录</h3>

      <el-form :model="loginForm" :rules="loginRules" ref="loginForm" label-width="0px">
        <!--用户名-->
        <el-form-item label="" prop="username" style="margin-top:40px;">
          <el-row>

            <el-col :span='2'>
              <svg class="icon" aria-hidden="true">
                <use xlink:href="#icon-yonghuming"></use>
              </svg>
            </el-col>

            <el-col :span='22'>
              <el-input class="inps" placeholder='用户名' v-model="loginForm.username" clearable></el-input>
            </el-col>

          </el-row>
        </el-form-item>

        <!--密码-->
        <el-form-item label="" prop="password">
          <el-row>

            <el-col :span='2'>
              <svg class="icon" aria-hidden="true">
                <use xlink:href="#icon-mima"></use>
              </svg>
            </el-col>

            <el-col :span='22'>
              <el-input class="inps" placeholder='密码' show-password v-model="loginForm.password" clearable></el-input>
            </el-col>

          </el-row>
        </el-form-item>

        <!--验证码-->
        <el-form-item label="" prop="captcha">
          <el-row>

            <el-col :span='2'>
              <svg class="icon" aria-hidden="true">
                <use xlink:href="#icon-2yanzhengma"></use>
              </svg>
            </el-col>

            <el-col :span='12'>
              <el-input class="inps" placeholder='验证码' v-model="loginForm.captcha" clearable></el-input>
            </el-col>

            <!--三目的判断是为了解决图片异步刷新报404的问题-->
            <el-image :src="captchaImg ? captchaImg : ''" @click="getCaptcha"
              style="width: 150px;height: 40px;float: right"></el-image>

          </el-row>
        </el-form-item>

        <el-form-item label="" prop="remember" style="margin-top: 15px;">
          <el-checkbox v-model="loginForm.remember">记住密码</el-checkbox>
        </el-form-item>

        <el-form-item style="margin-top:20px;text-align: center">
          <el-button type="primary" round class="submitBtn" @click="login('loginForm')">登录</el-button>
        </el-form-item>
      </el-form>

    </div>
  </div>
</template>


<script>

import { generaMenu } from "@/util/menu";
import { loginApi } from '@/api';

export default {
  data() {
    return {
      canvas: null,
      context: null,
      stars: [], //星星数组
      shadowColorList: [
        "#39f",
        "#ec5707",
        "#b031d4",
        "#22e6c7",
        "#92d819",
        "#14d7f1",
        "#e23c66"
      ], //阴影颜色列表
      directionList: ["leftTop", "leftBottom", "rightTop", "rightBottom"], //星星运行方向
      speed: 50, //星星运行速度
      last_star_created_time: new Date(), //上次重绘星星时间
      Ball: class Ball {
        constructor(radius) {
          this.x = 0;
          this.y = 0;
          this.radius = radius;
          this.color = "";
          this.shadowColor = "";
          this.direction = "";
        }

        draw(context) {
          context.save();
          context.translate(this.x, this.y);
          context.lineWidth = this.lineWidth;
          var my_gradient = context.createLinearGradient(0, 0, 0, 8);
          my_gradient.addColorStop(0, this.color);
          my_gradient.addColorStop(1, this.shadowColor);
          context.fillStyle = my_gradient;
          context.beginPath();
          context.arc(0, 0, this.radius, 0, Math.PI * 2, true);
          context.closePath();

          context.shadowColor = this.shadowColor;
          context.shadowOffsetX = 0;
          context.shadowOffsetY = 0;
          context.shadowBlur = 10;

          context.fill();
          context.restore();
        }
      }, //工厂模式定义Ball类
      width: window.innerWidth,
      height: window.innerHeight,
      captchaImg: null,
      loginForm: {
        username: "",
        password: "",
        captcha: "",
        uuid: "",
        remember: false
      },
      loginRules: {
        username: [
          { required: true, message: "请输入用户名", trigger: "blur" }
        ],
        password: [
          { required: true, message: "请输入密码", trigger: "blur" }
        ],
        captcha: [
          { required: true, message: "请输入验证码", trigger: "blur" }
        ]
      }
    };
  },

  methods: {
    //提交登录
    login(forName) {
      this.$refs[forName].validate(async (valid) => {
        if (valid) {
          try {
            const data = {
              username: this.loginForm.username,
              password: this.loginForm.password,
              captcha: this.loginForm.captcha,
              uuid: this.loginForm.uuid
            };
            
            const response = await loginApi.login(data);
            if (response.data.code === 20000) {
              // 记住密码处理
              if (this.loginForm.remember) {
                // 生成密钥并加密
                const { key, exportedKey } = await this.generateSymmetricKey();
                const { iv, encryptedData } = await this.encryptData(
                  this.loginForm.password,
                  key
                );

                // 安全存储
                const encryDate = {
                  username: this.loginForm.username,
                  iv: iv,
                  encryptedData: encryptedData,
                  exportedKey: exportedKey
                }
                this.$store.commit("encrySave", encryDate);
              } else {
                // 清除所有相关数据
                this.$store.commit("encryRemove");
              }

              this.$store.commit("login", response.data.data);
              this.$message.success('登录成功！');
              this.$router.push('/');
              generaMenu();
            } else if (response.data.code === 40700 || response.data.code === 40800) {
              this.getCaptcha();
            }
          } catch (error) {
            console.error('登录失败:', error);
            this.$message.error('登录过程发生异常');
          }
        } else {
          this.$message.warning('请输入用户名和密码!');
        }
      })
    },

    //获取验证码
    getCaptcha() {
      loginApi.getCaptcha().then(response => {
        let data = response.data.data;
        this.captchaImg = data.captcha;
        this.loginForm.uuid = data.uuid;
        // 重置定时器确保间隔准确
        this.clearCaptchaTimer();
        this.startCaptchaTimer();
      }).catch(error => {
        this.$message.error('获取验证码失败，请重试');
      })
    },

    // 加载保存的登录凭证
    async loadRememberedCredentials() {
      try {
        const encryData = this.$store.getters.getEncryData;
        const rememberedUser = encryData.username;
        const encryptedPass = encryData.encryptedData;
        const iv = encryData.iv;
        const exportedKey = encryData.exportedKey;

        if (rememberedUser && encryptedPass && iv && exportedKey) {
          // 异步解密
          const password = await this.decryptData(encryptedPass, iv, exportedKey);

          // 填充表单
          this.loginForm.username = rememberedUser;
          this.loginForm.password = password;
          this.loginForm.remember = true;
        }
      } catch (error) {
        console.error('解密失败:', error);
        // 自动清除损坏的凭证
        this.$store.commit("encryRemove");
        this.$message.warning('自动登录信息已过期，请重新输入');
      }
    },

    // ArrayBuffer 转 Base64
    arrayBufferToBase64(buffer) {
      return btoa(String.fromCharCode(...new Uint8Array(buffer)));
    },

    // Base64 转 ArrayBuffer
    base64ToArrayBuffer(base64) {
      const binaryString = atob(base64);
      const bytes = new Uint8Array(binaryString.length);
      for (let i = 0; i < binaryString.length; i++) {
        bytes[i] = binaryString.charCodeAt(i);
      }
      return bytes.buffer;
    },

    // 生成并导出密钥
    async generateSymmetricKey() {
      const key = await crypto.subtle.generateKey(
        { name: "AES-GCM", length: 256 },
        true,
        ["encrypt", "decrypt"]
      );
      const exportedKey = await crypto.subtle.exportKey("raw", key);
      return { key, exportedKey: this.arrayBufferToBase64(exportedKey) };
    },

    // 加密密码
    async encryptData(data, key) {
      const iv = crypto.getRandomValues(new Uint8Array(12));
      const encryptedData = await crypto.subtle.encrypt(
        { name: "AES-GCM", iv },
        key,
        new TextEncoder().encode(data)
      );
      return {
        iv: this.arrayBufferToBase64(iv.buffer),
        encryptedData: this.arrayBufferToBase64(encryptedData)
      };
    },

    // 解密密码
    async decryptData(encryptedDataBase64, ivBase64, exportedKeyBase64) {
      const keyBuffer = this.base64ToArrayBuffer(exportedKeyBase64);
      const key = await crypto.subtle.importKey(
        "raw",
        keyBuffer,
        { name: "AES-GCM" },
        true,
        ["decrypt"]
      );
      const encryptedData = this.base64ToArrayBuffer(encryptedDataBase64);
      const iv = this.base64ToArrayBuffer(ivBase64);
      const decrypted = await crypto.subtle.decrypt(
        { name: "AES-GCM", iv },
        key,
        encryptedData
      );
      return new TextDecoder().decode(decrypted);
    },

    //重复动画
    drawFrame() {
      let animation = requestAnimationFrame(this.drawFrame);
      this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
      this.createStar(false);
      this.stars.forEach(this.moveStar);
    },

    //展示所有的星星
    createStar(params) {
      let now = new Date();
      if (params) {
        //初始化星星
        for (var i = 0; i < 50; i++) {
          const radius = Math.random() * 3 + 2;
          let star = new this.Ball(radius);
          star.x = Math.random() * this.canvas.width + 1;
          star.y = Math.random() * this.canvas.height + 1;
          star.color = "#ffffff";
          star.shadowColor = this.shadowColorList[
            Math.floor(Math.random() * this.shadowColorList.length)
          ];
          star.direction = this.directionList[
            Math.floor(Math.random() * this.directionList.length)
          ];
          this.stars.push(star);
        }
      } else if (!params && now - this.last_star_created_time > 3000) {
        //每隔3秒重绘修改颜色其中30个球阴影颜色
        for (var i = 0; i < 30; i++) {
          this.stars[i].shadowColor = this.shadowColorList[
            Math.floor(Math.random() * this.shadowColorList.length)
          ];
        }
        this.last_star_created_time = now;
      }
    },

    //移动
    moveStar(star, index) {
      if (star.y - this.canvas.height > 0) {
        //触底
        if (Math.floor(Math.random() * 2) === 1) {
          star.direction = "leftTop";
        } else {
          star.direction = "rightTop";
        }
      } else if (star.y < 2) {
        //触顶
        if (Math.floor(Math.random() * 2) === 1) {
          star.direction = "rightBottom";
        } else {
          star.direction = "leftBottom";
        }
      } else if (star.x < 2) {
        //左边
        if (Math.floor(Math.random() * 2) === 1) {
          star.direction = "rightTop";
        } else {
          star.direction = "rightBottom";
        }
      } else if (star.x - this.canvas.width > 0) {
        //右边
        if (Math.floor(Math.random() * 2) === 1) {
          star.direction = "leftBottom";
        } else {
          star.direction = "leftTop";
        }
      }
      if (star.direction === "leftTop") {
        star.y -= star.radius / this.speed;
        star.x -= star.radius / this.speed;
      } else if (star.direction === "rightBottom") {
        star.y += star.radius / this.speed;
        star.x += star.radius / this.speed;
      } else if (star.direction === "leftBottom") {
        star.y += star.radius / this.speed;
        star.x -= star.radius / this.speed;
      } else if (star.direction === "rightTop") {
        star.y -= star.radius / this.speed;
        star.x += star.radius / this.speed;
      }
      star.draw(this.context);
    },

    // 启动定时器
    startCaptchaTimer() {
      this.captchaTimer = setInterval(() => {
        this.getCaptcha();
      }, 1000 * 60 * 3); // 3分钟
    },
    // 清理定时器
    clearCaptchaTimer() {
      if (this.captchaTimer) {
        clearInterval(this.captchaTimer);
        this.captchaTimer = null;
      }
    },
  },

  beforeDestroy() {
    // 组件销毁前清除定时器
    this.clearCaptchaTimer();
  },


  mounted() {
    this.canvas = document.getElementById("myCanvas");
    this.context = this.canvas.getContext("2d");

    this.createStar(true);
    this.drawFrame();

    this.getCaptcha();
    this.startCaptchaTimer();
  },

  created() {
    // 加载保存的登录凭证
    this.loadRememberedCredentials();
  },
};
</script>

<style lang='less' scoped>
#login {
  width: 100%;
  height: 100%;
  font-size: 30px;
  background-repeat: no-repeat;
  background-position: left top;
  background-color: #242645;
  color: #fff;
  font-family: "Source Sans Pro", serif;
  background-size: 100% 100%;
  background-image: url("../../assets/img/login.png");
  position: fixed;

  #bgd {
    height: 100vh;
    width: 100vw;
    overflow: hidden;
  }

  #loginBox {
    width: 400px;
    height: 320px;
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    margin: auto;
    padding: 40px 40px 80px 40px;
    box-shadow: -15px 15px 15px rgba(6, 17, 47, 0.7);
    opacity: 1;
    background: linear-gradient(230deg,
        rgba(53, 57, 74, 0) 0%,
        rgb(0, 0, 0) 100%);

    /deep/ .inps input {
      border-width: 1px;
      color: #fff;
      background-color: transparent;
      font-size: 17px;
    }

    .submitBtn {
      background-color: transparent;
      color: #39f;
      width: 200px;
    }

    .icon {
      width: 2em;
      height: 2em;
      vertical-align: -0.7em;
      fill: currentColor;
      overflow: hidden;
    }

  }
}
</style>
